#include "protocol.h"
#include "common.h"
#include "../lookup/lookup.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

RipngErrorCode disassemble(const uint8_t *packet, uint32_t len,
                           RipngPacket *output) {
    if (len < sizeof(struct ip6_hdr)) {
        return RipngErrorCode::ERR_LENGTH;
    }
    struct ip6_hdr *ip6 = (struct ip6_hdr *) packet;
    uint16_t nxt_header = ip6->ip6_nxt;
    if (nxt_header == IPPROTO_UDP) {
        uint16_t ip6_payload_len = ntohs(ip6->ip6_plen);
        if (ip6_payload_len < sizeof(struct udphdr)) {
            return RipngErrorCode::ERR_LENGTH;
        }
        struct udphdr *udp = (struct udphdr *) &packet[sizeof(struct ip6_hdr)];
        if (ip6->ip6_plen != udp->len) {
            return RipngErrorCode::ERR_LENGTH;
        }
        if (ntohs(udp->uh_sport) != 521 || ntohs(udp->uh_dport) != 521) {
            return RipngErrorCode::ERR_UDP_PORT_NOT_RIPNG;
        }
        uint16_t udp_len = ntohs(udp->len);
        uint32_t rip_entries_len = udp_len - sizeof(struct udphdr) - sizeof(struct ripng_hdr);
        if (rip_entries_len % sizeof(ripng_rte) != 0) {
            return RipngErrorCode::ERR_LENGTH;
        }
        struct ripng_hdr *ripng = (struct ripng_hdr *) &packet[sizeof(struct ip6_hdr) + sizeof(struct udphdr)];
        if (ripng->command != 1 && ripng->command != 2) {
            return RipngErrorCode::ERR_RIPNG_BAD_COMMAND;
        }
        if (ripng->version != 1) {
            return RipngErrorCode::ERR_RIPNG_BAD_VERSION;
        }
        if (ripng->zero != 0) {
            return RipngErrorCode::ERR_RIPNG_BAD_ZERO;
        }
        output->command = ripng->command;
        output->numEntries = rip_entries_len / sizeof(ripng_rte);
        const uint8_t *addr = &packet[sizeof(struct ip6_hdr) + sizeof(struct udphdr) + sizeof(struct ripng_hdr)];
        for (int i = 0; i < output->numEntries; ++i) {
            ripng_rte *e = (ripng_rte *) addr;
            uint8_t prefix_len = e->prefix_len;
            uint8_t metric = e->metric;
            if (metric == 0xff) {
                if (prefix_len != 0) {
                    return RipngErrorCode::ERR_RIPNG_BAD_PREFIX_LEN;
                }
                if (e->route_tag != 0) {
                    return RipngErrorCode::ERR_RIPNG_BAD_ROUTE_TAG;
                }
            } else {
                if (metric < 1 || metric > 16) {
                    return RipngErrorCode::ERR_RIPNG_BAD_METRIC;
                }
                // printf("prefix_len: %u\n", prefix_len);
                if (prefix_len > 128) {
                    return RipngErrorCode::ERR_RIPNG_BAD_PREFIX_LEN;
                }
                // printf("mask_to_len(e->prefix_or_nh): %u\n", mask_to_len(e->prefix_or_nh));
                if ((e->prefix_or_nh & len_to_mask(e->prefix_len)) != e->prefix_or_nh) {
                    return RipngErrorCode::ERR_RIPNG_INCONSISTENT_PREFIX_LENGTH;
                }
            }
            output->entries[i] = *e;
            addr += sizeof(ripng_rte);
        }
    } else {
        return RipngErrorCode::ERR_IPV6_NEXT_HEADER_NOT_UDP;
    }
    return RipngErrorCode::SUCCESS;
}

uint32_t assemble(const RipngPacket *ripng, uint8_t *buffer) {
    struct ripng_hdr *ripng_header = (struct ripng_hdr *) buffer;
    ripng_header->command = ripng->command;
    ripng_header->zero = 0;
    ripng_header->version = 1;
    int n = ripng->numEntries;
    for (int i = 0; i < n; ++i) {
        ripng_rte *e = (ripng_rte *) (buffer + sizeof(struct ripng_hdr) + sizeof(ripng_rte) * i);
        e->prefix_len = ripng->entries[i].prefix_len;
        e->prefix_or_nh = ripng->entries[i].prefix_or_nh;
        e->metric = ripng->entries[i].metric;
        e->route_tag = ripng->entries[i].route_tag;
    }
    return sizeof(struct ripng_hdr) + sizeof(ripng_rte) * n;
}